#pragma once

#include <iostream>
#include <string>
#include <filesystem> // path in C++17
#include <functional>
#include <utility>

#include <stdexcept>
#include <tuple>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <thread>
#include <cstdio> // remove file

#ifdef _WIN32
  #include <io.h>
  #include <wchar.h>
  #include <windows.h>
#endif

#if __cplusplus >= 201703L
#include <string_view>
#define TXDNN_STR_VIEW std::string_view
#else
// TODO to remove this as using C++ 17
// #include <nonstd/string_view.hpp> // in third-party
// #define TXDNN_STR_VIEW nonstd::string_view
#endif

#include "TxnnCpp/Common.h"


namespace common {

// https://en.cppreference.com/w/cpp/string/basic_string_view
// https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/experimental/string_view
// using string_view to improve performance of string operations
// https://github.com/martinmoene/string-view-lite

namespace fs = std::filesystem;

// TODO: implement string_view

} // namespace txdnn

inline std::string operator+(const TXDNN_STR_VIEW s, const txdnn::fs::path& p) {
  return p.string().insert(0, s);
}

inline std::string operator+(const txdnn::fs::path& p, const TXDNN_STR_VIEW s) {
  return p.string().append(s);
}


namespace txdnn {

#ifdef _WIN32
constexpr TXDNN_STR_VIEW executable_suffix{".exe"};
constexpr TXDNN_STR_VIEW library_prefix{""};
constexpr TXDNN_STR_VIEW dynamic_library_suffix{".dll"};
constexpr TXDNN_STR_VIEW static_library_suffix{".lib"};
constexpr TXDNN_STR_VIEW object_file_suffix{".obj"};
#else
constexpr TXDNN_STR_VIEW executable_suffix{""};
constexpr TXDNN_STR_VIEW library_prefix{"lib"};
constexpr TXDNN_STR_VIEW dynamic_library_suffix{".so"};
constexpr TXDNN_STR_VIEW static_library_suffix{".a"};
constexpr TXDNN_STR_VIEW object_file_suffix{".o"};
#endif // _WIN32

inline fs::path make_executable_name(const fs::path& p) {
  return p.parent_path() / (p.filename() + executable_suffix);
}

inline fs::path make_dynamic_library_name(const fs::path& p) {
  return p.parent_path() / (library_prefix + p.filename() + dynamic_library_suffix);
}

inline fs::path make_object_file_name(const fs::path& p) {
  return p.parent_path() / (p.filename() + object_file_suffix);
}

inline fs::path make_static_library_name(const fs::path& p) {
  return p.parent_path() / (library_prefix + p.filename() + static_library_suffix);
}

struct FsPathHash {
  std::size_t operator()(const fs::path& p) const noexcept {
    return fs::hash_value(p);
  }
};

struct SimpleHash {
  size_t operator()(const std::pair<fs::path, std::string>& p) const {
    return (fs::hash_value(p.first) ^ std::hash<std::string>()(p.second));
  }
};


/* -------------------------------------------------------------- */
void SleepMillis(unsigned int milli_seconds) _TX_NOEXCEPT;

/* File Utils*/
#define SEP "/"

bool PathExists(const std::string& fname) _TX_NOEXCEPT;

int RemoveFile(const std::string& fname) _TX_NOEXCEPT;

int RenameFile(const std::string& src, const std::string& dst) _TX_NOEXCEPT;

size_t FileSize(FILE* f);

bool WriteBytes(const void* ptr, const size_t n_bytes, FILE* fp);

bool FileSync(FILE* fp);

bool FileOpen(FILE** fp, const std::string& fname, const std::string& mode);

bool MkDir(const std::string& dir);

// return directory name from given path or empty string
// "dir/file"     -> "dir"
// "dir/"         -> "dir"
// "dir"          -> ""
// "dir///"       -> "dir//"
std::string DirName(const std::string& path);

bool CreateDir(const std::string& path);

} // namespace common